home *** CD-ROM | disk | FTP | other *** search
Wrap
# Source Generated with Decompyle++ # File: in.pyc (Python 2.4) ''' This module provides functionality to auto update the SpamExperts software. This includes updating the spamexperts.modules file that contains all the .pyc files that SpamExperts uses. The idea is that this file should be updated rather than replaced, as it is just a zipped collection of small files, the vast majority of which will not change during an update. Only needing to download those files that have changed will dramatically increase the speed of updates. ''' from __future__ import division import os import re import md5 import sys import types import errno import shutil import socket import urllib import gettext import marshal import zipfile import _winreg import urllib2 import fnmatch import winerror import platform import urlparse import tempfile import subprocess try: import cStringIO as StringIO except ImportError: import StringIO try: WindowsError except NameError: WindowsError = Exception import win32api import win32event import pywintypes try: import ntsecuritycon except ImportError: ntsecuritycon = None try: import win32security except ImportError: win32security = None from spamexperts import dis from spamexperts.Options import options from spamexperts.license import get_code from spamexperts.Version import versions from spamexperts.LSPControl import LSPControl from spamexperts.resources import application_directory try: _ except NameError: _ = gettext.gettext def AdjustPrivilege(priv, enable = True): if ntsecuritycon is None or win32security is None: raise pywintypes.error() flags = ntsecuritycon.TOKEN_ADJUST_PRIVILEGES | ntsecuritycon.TOKEN_QUERY htoken = win32security.OpenProcessToken(win32api.GetCurrentProcess(), flags) id = win32security.LookupPrivilegeValue(None, priv) if enable: newPrivileges = [ (id, ntsecuritycon.SE_PRIVILEGE_ENABLED)] else: newPrivileges = [ (id, 0)] win32security.AdjustTokenPrivileges(htoken, 0, newPrivileges) def NTReboot(message, timeout, bForce = False, bReboot = True): if ntsecuritycon is None or win32security is None: raise pywintypes.error() AdjustPrivilege(ntsecuritycon.SE_SHUTDOWN_NAME) try: win32api.InitiateSystemShutdown(None, message, timeout, bForce, bReboot) finally: AdjustPrivilege(ntsecuritycon.SE_SHUTDOWN_NAME, False) def Reboot(): try: NTReboot('', 0) except pywintypes.error: platform = platform.release() if platform in ('95', '98', 'ME'): subprocess.Popen('rundll32.exe shell32.dll,SHExitWindowsEx 2') elif platform in ('NT', '2K'): subprocess.Popen('shutdown.exe /L /R') elif platform in ('XP', '2003'): subprocess.Popen('shutdown.exe -r') else: print >>sys.stderr, 'Unsupported Windows version.' except: platform in ('95', '98', 'ME') class VersionControl(object): '''This class provides the auto update functionality for an application.''' def getVersionFromServer(self): '''Get the current version from the update server. Returns the version or None on error. ''' timeout = socket.getdefaulttimeout() socket.setdefaulttimeout(15) if get_code(): url = 'http://%s/%s' % (options[('globals', 'professional_update_server')], options[('globals', 'software_version_file')]) else: url = 'http://%s/%s' % (options[('globals', 'software_update_server')], options[('globals', 'software_version_file')]) try: data = urllib2.urlopen(url).read() except (urllib2.HTTPError, urllib2.URLError, socket.error): e = None print >>sys.stderr, 'Error checking for update:', e data = None socket.setdefaulttimeout(timeout) return data def getVersionFromRegistry(self): '''Get the installed version from the registry. Returns the version or None on error. ''' key = _winreg.CreateKey(_winreg.HKEY_LOCAL_MACHINE, 'Software\\SpamExperts') try: return _winreg.QueryValueEx(key, 'LatestBuild')[0] except WindowsError: return None def checkForUpdate(self): '''Check for an update. Returns True if an update is available, false otherwise. ''' available_version = self.getVersionFromServer() if available_version: release_date = float(available_version) if release_date < 1142370460: return False current_version = self.getVersionFromRegistry() if current_version is None: return False try: available_version_number = float(available_version) current_version_number = float(current_version) except ValueError: return False if available_version_number < current_version_number: print >>sys.stderr, 'Available version is older than current.' return False elif available_version_number == current_version_number: if options[('globals', 'verbose')]: print 'Using most recent version.' return False return True return False def doUpdate(self): '''Perform an update.''' return subprocess.Popen(options[('globals', 'software_updater')]) class Update(object): '''Updater base class. Must be subclassed to be used.''' def __init__(self, exe_dir, force = False): self.temp_dir = os.path.join(win32api.GetTempPath(), 'spamexperts_update') self.backup_dir = os.path.join(self.temp_dir, 'backup') try: os.makedirs(self.backup_dir) except OSError: pass self.exe_dir = exe_dir self.force = force self.files_to_replace = [] self.replace_on_restart = [] self.backup_files = [] def all_files(self, directory, files): '''Recursively build a list of all files inside the given directory, using paths relative to the initial directory.''' for fn in os.listdir(directory): full_fn = os.path.join(directory, fn) if os.path.isdir(full_fn): self.all_files(full_fn, files) continue files.append(full_fn) return files def checksum(fn): '''Return a simple checksum of the specified file.''' return md5.md5(file(fn, 'rb').read()).hexdigest() checksum = staticmethod(checksum) FAILED = 'f' SUCCEEDED = 's' RESTART = 'r' SUCCEEDED_NO_REPLACE = 'n' def download_files(self, update_list_filename, callback, directory_name = None): '''Replace (if necessary) the files in update_list_filename. As the update progresses, call callback with the percentage complete and any status messages.''' if directory_name is None: directory_name = self.exe_dir total_replaced = 0 self.files_to_replace = [] restart_required = False if get_code(): base_url = 'http://' + options[('globals', 'professional_update_server')] else: base_url = 'http://' + options[('globals', 'software_update_server')] all_files = file(update_list_filename).read().split('\n\n') total = len(all_files) for i, item in enumerate(all_files): if options[('globals', 'verbose')]: print 'Starting download of', repr(item) if not item: continue try: (local, remote, checksum) = item.strip().split('\n') except ValueError: return self.FAILED callback((i / total) * 100, _('Updating %s') % (local,)) abs_local = os.path.join(directory_name, local) if not os.path.exists(abs_local): local_checksum = None else: local_checksum = self.checksum(abs_local) if local_checksum != checksum or self.force: result = self.download_file(base_url, remote, local, checksum, abs_local) if result == self.FAILED: return self.FAILED elif result == self.RESTART: restart_required = True total_replaced += 1 continue urllib.urlcleanup() if restart_required: return self.RESTART if not total_replaced: return self.SUCCEEDED_NO_REPLACE return self.SUCCEEDED def download_file(self, base_url, remote, local, checksum, abs_local): remote_file = urlparse.urljoin(base_url, remote) local_temp = os.path.join(self.temp_dir, local) if os.path.exists(local_temp) and self.checksum(local_temp) == checksum: replacement = local_temp else: try: os.makedirs(os.path.dirname(local_temp)) except OSError: pass f = file(local_temp, 'wb') try: url = urllib2.urlopen(remote_file) f.write(url.read()) except (urllib2.HTTPError, urllib2.URLError, socket.error): e = None if options[('globals', 'verbose')]: print 'Download error', str(e) return self.FAILED f.close() replacement = local_temp verify_checksum = self.checksum(replacement) if verify_checksum != checksum: if options[('globals', 'verbose')]: print 'Checksum mismatch', remote_file, verify_checksum, checksum return self.FAILED self.files_to_replace.append((abs_local, replacement)) if options[('globals', 'verbose')]: print 'Downloaded', replacement return self.SUCCEEDED def replace_files(self): result = self.SUCCEEDED self.files_to_replace = list(set(self.files_to_replace)) while True: try: (old, new) = self.files_to_replace.pop() except IndexError: break backup = os.path.join(self.backup_dir, old) try: os.remove(backup) except OSError: pass try: os.makedirs(os.path.dirname(backup)) except OSError: pass if os.path.exists(old): try: os.rename(old, backup) if os.path.exists(old): err = OSError() err.errno = errno.EACCES raise err except OSError: e = None if e.errno == errno.EACCES: self.replace_on_restart.append((old, new)) result = self.RESTART if options[('globals', 'verbose')]: print 'Replacing on restart', old continue continue elif options[('globals', 'verbose')]: print 'File replace failed:', str(e), old, new self.files_to_replace.append((old, new)) return self.FAILED self.backup_files.append((backup, old)) if not os.path.isdir(os.path.dirname(old)): os.makedirs(os.path.dirname(old)) try: os.rename(new, old) except OSError: e = None print >>sys.stderr, "Couldn't replace %s (%s). Giving up." % (old, str(e)) return self.FAILED if options[('globals', 'verbose')]: print 'Replaced', old continue return result def undo(self): while True: try: (backup, original) = self.backup_files.pop() except IndexError: break try: os.remove(original) except OSError: pass os.rename(backup, original) def generate_filelist(self, directory_name, update_list_filename): raise NotImplementedError def cleanup(self): if os.path.exists(self.temp_dir): try: shutil.rmtree(self.temp_dir) except OSError: e = None print 'Could not clean up', str(e) except: None<EXCEPTION MATCH>OSError None<EXCEPTION MATCH>OSError class UpdateModules(Update): """Update the spamexperts.modules zip file that contains all the byte-compiled Python modules that the application uses. Files can't be removed from a zip file. We could simply add the new files to the zip, and these will be the ones that are imported, but that relies on an implementation detail and means that the zip file will continually increase in size. What we do instead is unzip the modules file to a temporary location, replace the files that need replacing, and then zip it up again.""" def __init__(self, exe_dir, force = False): Update.__init__(self, exe_dir, force) self.zip_dir = os.path.join(self.temp_dir, 'z') def _disassemble(codeobject): dis_output = StringIO.StringIO() dis.dis(codeobject, dis_output) dis_output.seek(0) return dis_output.read() _disassemble = staticmethod(_disassemble) address_re = re.compile('<.*[0-9a-zA-Z]+>', re.DOTALL) def _get_code_from_object(cls, codeobject): code = set() code.add(cls.address_re.sub('', cls._disassemble(codeobject))) for item in codeobject.co_consts: if isinstance(item, types.CodeType): code.add(cls._get_code_from_object(item)) continue code.add(cls.address_re.sub('', repr(item))) return ''.join(code) _get_code_from_object = classmethod(_get_code_from_object) def old_get_code_from_object(class_, codeobject): code = [] for item in codeobject.co_consts: if isinstance(item, types.CodeType): code.append(class_._get_code_from_object(item)) continue code.append(class_.address_re.sub('', repr(item))) return ''.join(code) old_get_code_from_object = classmethod(old_get_code_from_object) def checksum(cls, fn): pyc_codeobject = marshal.loads(file(fn, 'rb').read()[8:]) code = cls._get_code_from_object(pyc_codeobject) unique_string = '%s%s%s' % (code, repr(pyc_codeobject.co_names), pyc_codeobject.co_code) return md5.md5(unique_string).hexdigest() checksum = classmethod(checksum) def unzip(self, zip_fn): '''Unzip the given zipfile to the temp directory.''' zip = zipfile.ZipFile(zip_fn) for each in zip.namelist(): if not each.endswith('/'): (root, name) = os.path.split(each) directory = os.path.normpath(os.path.join(self.zip_dir, root)) if not os.path.isdir(directory): os.makedirs(directory) file(os.path.join(directory, name), 'wb').write(zip.read(each)) def zip(self, zip_fn): '''Create a zipfile of the temp directory and store it at the specified location.''' zf = zipfile.ZipFile(zip_fn, 'w') f = self.all_files(self.zip_dir, []) offset = len(self.zip_dir) + 1 for fn in f: zf.write(fn, fn[offset:]) zf.close() def generate_filelist(self, zip_fn, update_list_filename): if os.path.exists(self.zip_dir): shutil.rmtree(self.zip_dir) self.unzip(zip_fn) f = self.all_files(self.zip_dir, []) output = file(update_list_filename, 'w') offset = len(self.zip_dir) + 1 for fn in f: base = fn[offset:] output.write('\n'.join((base, 'dist/modules/' + base.replace('\\', '/'), self.checksum(fn), '', ''))) output.close() def download_files(self, update_list_filename, callback, zip_fn): local_callback = lambda i, s: callback(10 + i * 0.80000000000000004, s) callback(0, _('Decompressing spamexperts.modules')) self.unzip(zip_fn) self.exe_dir = self.zip_dir result = Update.download_files(self, update_list_filename, local_callback) if result != self.SUCCEEDED: return result result = self.replace_files() if result != self.SUCCEEDED: if options[('globals', 'verbose')]: print 'Not replacing spamexperts.modules' return result callback(80, _('Recompressing spamexperts.modules')) new_name = zip_fn + '.new' self.zip(new_name) self.files_to_replace.append((zip_fn, new_name)) callback(100, _('spamexperts.modules update complete')) return self.SUCCEEDED class UpdateGeneral(Update): '''Update files outside of zipped modules file.''' def generate_filelist(self, directory_name, update_list_filename, excludes = ()): offset = len(directory_name) + 1 f = self.all_files(directory_name, []) for excluded in excludes: f = _[1] directory_name = directory_name.replace('\\', '/') + '/' output = file(update_list_filename, 'w') for fn in f: base = fn[offset:] posix_base = base.replace('\\', '/') output.write('\n'.join((base, urlparse.urljoin(directory_name, posix_base), self.checksum(fn), '', ''))) output.close() def generate_update_files(): '''Utility function to generate all update files. Assumes current working directory is py2exe. ''' general = UpdateGeneral(None) general.generate_filelist('dist', 'general.txt', (os.path.join('modules', '*'), os.path.join('lib', 'spamexperts.modules'), os.path.join('external_dlls', '*'), os.path.join('lsp', '*'))) general.generate_filelist(os.path.join('dist', 'lsp'), 'lsps.txt') general.generate_filelist(os.path.join('dist', 'external_dlls'), 'sysfiles.txt', ('SpamExpertsLSP.ini',)) modules = UpdateModules(None) modules.generate_filelist(os.path.join('dist', 'lib', 'spamexperts.modules'), 'modules.txt') class CompleteUpdate(object): title = versions['Apps']['SpamExperts']['Description'] name = title + _('Update') reg_section = _winreg.HKEY_LOCAL_MACHINE reg_key = 'Software\\SpamExperts' sys_dir = win32api.GetSystemDirectory() update_lists = ('general.txt', 'lsps.txt', 'sysfiles.txt', 'modules.txt') if get_code(): base_url = 'http://' + options[('globals', 'professional_update_server')] else: base_url = 'http://' + options[('globals', 'software_update_server')] def __init__(self, tray = None): self.exe_dir = application_directory() self.LSP = LSPControl() self.tray = tray self.temp_dir = os.path.join(win32api.GetTempPath(), 'spamexperts_update') try: os.makedirs(self.temp_dir) except OSError: pass def read_key(self, name): key = _winreg.CreateKey(self.reg_section, self.reg_key) try: return _winreg.QueryValueEx(key, name)[0] except WindowsError: pass def write_key(self, name, value): if not isinstance(value, types.StringTypes): raise AssertionError, 'Can only write strings.' key = _winreg.CreateKey(self.reg_section, self.reg_key) _winreg.SetValueEx(key, name, 0, _winreg.REG_SZ, value) def emergency_check(self): import wx if get_code(): base_url = 'http://' + options[('globals', 'professional_update_server')] else: base_url = 'http://' + options[('globals', 'software_update_server')] try: url = urllib2.urlopen(urlparse.urljoin(base_url, 'emergency.txt')) text = url.read() except (urllib2.HTTPError, urllib2.URLError, socket.error): e = None if hasattr(e, 'code') and e.code == 404: pass else: print >>sys.stderr, "Couldn't check for emergency text", str(e) return False initial_text = 'SpamExperts Emergency' notice_text = 'SpamExperts Notice' if text.lower().startswith(initial_text.lower()): text = text[len(initial_text):].strip() result = True elif text.lower().startswith(notice_text.lower()): text = text[len(notice_text):].strip() result = False else: return False flags = wx.OK | wx.ICON_INFORMATION d = wx.MessageDialog(None, text, self.title, flags) d.ShowModal() d.Destroy() return result def conclude_update_needed(self): if self.read_key('update_in_progress') == 'True': return True self.callback = lambda x, y: (x, y) general = UpdateGeneral(self.exe_dir) modules = UpdateModules(self.exe_dir) self.cleanup((), general, general, general, modules) return False def conclude_update(self, callback = None): if not callback: print 'Bad call to conclude update. Aborting.' return None if options[('globals', 'verbose')]: print 'Concluding update' if self.read_key('run_copyfile') == 'True': if options[('globals', 'verbose')]: print 'Copyfile has not run; retrying.' in_use_fn = os.path.join(tempfile.gettempdir(), 'spamexperts_update', 'in_use.txt') subprocess.Popen('copyfile.exe "' + in_use_fn + '"', shell = True, cwd = self.exe_dir) return None if self.read_key('resume_LSP_install') == 'True': if options[('globals', 'verbose')]: print 'Registering LSP' self.LSP.installLSP() self.write_key('resume_LSP_install', 'False') self.callback = callback general = UpdateGeneral(self.exe_dir) modules = UpdateModules(self.exe_dir) self.write_build_number(self.base_url) self.cleanup(self.update_lists, general, general, general, modules) self.notify_success() self.write_key('update_in_progress', 'False') def download_txt_file(self, remote_file, local_file): import wx while True: try: url = urllib2.urlopen(remote_file) content = url.read() except (urllib2.HTTPError, urllib2.URLError, socket.error): e = None if '<html>' not in content.lower(): file(local_file, 'wb').write(content) return True flags = wx.YES_NO | wx.ICON_QUESTION d = wx.MessageDialog(None, _('File download failed. Retry?'), self.title, flags) result = d.ShowModal() d.Destroy() if result == wx.ID_NO: return False continue def update(self, callback): import wx if self.emergency_check(): return None self.write_key('update_in_progress', 'True') self.callback = callback self.general_callback = lambda i, s: callback(5 + i * 0.34999999999999998, s) self.lsp_callback = lambda i, s: callback(40 + i * 0.074999999999999997, s) self.external_callback = lambda i, s: callback(47.5 + i * 0.074999999999999997, s) self.modules_callback = lambda i, s: callback(55 + i * 0.29999999999999999, s) self.reboot_required = False self.restart_required = False general = UpdateGeneral(self.exe_dir) lsp = UpdateGeneral(self.exe_dir) external = UpdateGeneral(self.exe_dir) modules = UpdateModules(self.exe_dir) update_lists = self.update_lists base_url = self.base_url self.callback(1, _('Downloading update list')) print 'Downloading update from', base_url for i, update_list in enumerate(update_lists): self.callback(1 + 4 * i / len(update_lists), _('Downloading %s') % (update_list,)) remote_file = urlparse.urljoin(base_url, update_list) local_temp = os.path.join(self.temp_dir, update_list) if not self.download_txt_file(remote_file, local_temp): return None continue self.callback(5, _('Update list download complete')) general_replaced = self.process_list(general, update_lists[0], self.general_callback, self.exe_dir) if options[('globals', 'verbose')]: print 'General replacement: ', general_replaced if general_replaced is None: return None lsps_replaced = self.process_list(lsp, update_lists[1], self.lsp_callback, self.sys_dir) if options[('globals', 'verbose')]: print 'LSPS replacement: ', lsps_replaced if lsps_replaced is None: return None external_replaced = self.process_list(external, update_lists[2], self.external_callback, self.exe_dir) if options[('globals', 'verbose')]: print 'External replacement: ', external_replaced if external_replaced is None: return None zip_fn = os.path.join(self.exe_dir, 'lib', 'spamexperts.modules') modules_replaced = self.process_list(modules, update_lists[3], self.modules_callback, zip_fn) if options[('globals', 'verbose')]: print 'Modules replacement: ', modules_replaced if modules_replaced is None: return None self.callback(87.5, _('Replacing files...')) result = self.do_replace(general) if result is None: general.undo() return None self.callback(89, _('Replacing files...')) result = self.do_replace(lsp) if result is None: general.undo() lsp.undo() return None self.callback(91, _('Replacing files...')) result = self.do_replace(external) if result is None: general.undo() lsp.undo() external.undo() return None self.callback(92.5, _('Replacing modules...')) self.do_replace(modules) if result is None: general.undo() lsp.undo() external.undo() modules.undo() return None if lsps_replaced == general.SUCCEEDED or lsps_replaced == general.RESTART: self.reboot_required = True if options[('globals', 'verbose')]: print 'Unregistering LSP' self.LSP.removeLSP() self.write_key('resume_LSP_install', 'True') if options[('globals', 'verbose')]: print 'Rebooting' self.reboot() return None if self.restart_required: self.callback(100, _('Restarting updater to replace in-use files.')) msg = _('SpamExperts must restart in order to complete this update. Please finish adjusting settings or classifying messages and exit SpamExperts (the application will automatically restart).') if hasattr(self, 'balloon'): self.balloon(msg, subprocess.Popen, ('copyfile.exe "' + self._in_use_fn + '"',), { 'shell': True, 'cwd': self.exe_dir }) else: print >>sys.stderr, "Couldn't present update balloon." subprocess.Popen('copyfile.exe "' + self._in_use_fn + '"', shell = True, cwd = self.exe_dir) return None self.write_build_number(base_url) self.cleanup(update_lists, general, lsp, external, modules) self.notify_success() def cleanup(self, update_lists, general, lsp, external, modules): self.callback(97, _('Cleaning up after update.')) for update_list in update_lists: try: os.remove(os.path.join(self.temp_dir, update_list)) continue except OSError: continue general.cleanup() lsp.cleanup() external.cleanup() modules.cleanup() def write_build_number(self, base_url): self.callback(99, _('Registering updated version.')) remote_file = urlparse.urljoin(base_url, 'version.txt') local_file = os.path.join(self.temp_dir, 'version.txt') timeout = socket.getdefaulttimeout() socket.setdefaulttimeout(15) if not self.download_txt_file(remote_file, local_file): socket.setdefaulttimeout(timeout) return None socket.setdefaulttimeout(timeout) version = file(local_file).read().strip() urllib.urlcleanup() self.write_key('LatestBuild', version) def notify_success(self): msg = _('%s has been updated successfully.') % (self.title,) self.callback(100, msg) self.write_key('update_in_progress', 'False') def _reattempt(self, func, args, failed, restart): import wx while True: result = func(*args) if result == failed: flags = wx.YES_NO | wx.ICON_QUESTION d = wx.MessageDialog(None, _('File replacement failed. Retry?'), self.title, flags) result = d.ShowModal() d.Destroy() if result == wx.ID_NO: return None result == wx.ID_NO if result == restart: self.restart_required = True break return result def in_use_file(self): if hasattr(self, '_in_use_fn'): first_open = False f = file(self._in_use_fn, 'a') else: first_open = True self._in_use_fn = os.path.join(win32api.GetTempPath(), 'spamexperts_update', 'in_use.txt') f = file(self._in_use_fn, 'w') self.write_key('run_copyfile', 'True') if first_open and self.reboot_required: if options[('globals', 'verbose')]: print 'Files will be replaced on reboot.' f.write('\n') elif first_open: if options[('globals', 'verbose')]: print 'Files will be replaced on application restart.' f.write('"%s"\n' % (sys.executable,)) return f def do_replace(self, updater): result = self._reattempt(updater.replace_files, (), updater.FAILED, updater.RESTART) if updater.replace_on_restart: f = self.in_use_file() for old, new in updater.replace_on_restart: f.write('%s\n%s\n' % (new, old)) f.close() self.restart_required = True return result def process_list(self, updater, update_list, callback, directory): return self._reattempt(updater.download_files, (os.path.join(self.temp_dir, update_list), callback, directory), updater.FAILED, updater.RESTART) def start_on_reboot(name, command): if options[('globals', 'verbose')]: print 'Executing on restart:', command key = _winreg.CreateKey(_winreg.HKEY_LOCAL_MACHINE, 'Software\\Microsoft\\Windows\\CurrentVersion\\RunOnce') _winreg.SetValueEx(key, name, 0, _winreg.REG_SZ, command) start_on_reboot = staticmethod(start_on_reboot) def reboot(self): import wx self.write_key('need_reboot', 'True') if self.restart_required: cmd = os.path.join(self.exe_dir, 'copyfile.exe ' + self._in_use_fn) self.start_on_reboot('spamexperts_update', cmd) flags = wx.YES_NO | wx.ICON_QUESTION msg = _('%s must restart your computer for this update to be completed.\r\n%s will re-launch when Windows boots up next.\r\nWould you like to restart now?') % (self.name, self.name) d = wx.MessageDialog(None, msg, self.title, flags) result = d.ShowModal() d.Destroy() if result == wx.ID_OK: Reboot() if __name__ == '__main__': v = VersionControl() print v.getVersionFromServer()